/**
 * 
 */
package model.session;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import model.preprocessor.PreProcessorResultStatus;
import model.solver.SolverResultStatus;
import controller.xml.XMLBaliseFormat;


/**
 * Represent a session. This means a successions of event, basically solver or preprocessor
 * execution on a beginning CNF that lead to other execution on the cnf that result from the
 * previous execution ...
 * 
 * @author GRONOWSKI Stephane svan001@aol.com
 * 
 *         igsat 2010
 * 
 */
public class Session {

	private List<SessionBlock>	shortestPath	= null;
	private CNFUse				first;

	/**
	 * Create a session using the specified {@link CNFUse} as first block.
	 * 
	 * @param first
	 *            the cnf to use
	 */
	public Session(CNFUse first) {
		this.first = first;
	}

	/**
	 * Return the first CNFUse.
	 * 
	 * @return the first CNFUse.
	 */
	public CNFUse getFirst() {
		return first;
	}

	/**
	 * Return if this session contains the specified CFNUse.
	 * 
	 * @param cnf
	 *            a cnf
	 * @return if this session contains the specified CFNUse.
	 */
	public boolean containsCNFUse(CNFUse cnf) {
		return contains(first, cnf);
	}

	private boolean contains(CNFUse seekIn, CNFUse isseek) {
		if (seekIn == isseek)
			return true;
		for (CNFUse p : seekIn.getPreproc().values())
			if (p != null && contains(p, isseek))
				return true;

		return false;

	}

	/**
	 * Return the {@link CNFUse} that contains the specified {@link SolverExecutionBlock}.
	 * 
	 * @param solver
	 *            the {@link SolverExecutionBlock} to seek
	 * @return the {@link CNFUse} that contains it or null
	 */
	public CNFUse containsSolver(SolverExecutionBlock solver) {
		return containsSolver2(first, solver);
	}

	private CNFUse containsSolver2(CNFUse seekIn, SolverExecutionBlock solver) {
		if (seekIn.getSolver().contains(solver))
			return seekIn;
		for (CNFUse p : seekIn.getPreproc().values())
		{
			CNFUse res = containsSolver2(p, solver);
			if (res != null)
				return res;
		}
		return null;

	}

	/**
	 * Return the {@link CNFUse} that contains the specified {@link PreprocessorExecutionBlock}.
	 * 
	 * @param preproc
	 *            the {@link PreprocessorExecutionBlock} to seek
	 * @return the {@link CNFUse} that contains it or null
	 */
	public CNFUse containsPreprocessor(PreprocessorExecutionBlock preproc) {
		return containsPreprocessor2(first, preproc);
	}

	private CNFUse containsPreprocessor2(CNFUse seekIn, PreprocessorExecutionBlock preproc) {
		if (seekIn.getPreproc().keySet().contains(preproc))
			return seekIn;
		for (CNFUse p : seekIn.getPreproc().values())
		{
			if (p == null)
				continue;
			CNFUse res = containsPreprocessor2(p, preproc);
			if (res != null)
				return res;
		}
		return null;

	}


	/**
	 * Close the session, means basically close all the cnf.
	 * 
	 * @return false if there was a problem.
	 */
	public boolean close() {
		return closeCNFUse(first);
	}

	/**
	 * Close the {@link CNFUse}, means basically close all the cnf associate.
	 * 
	 * @param cnf
	 *            the {@link CNFUse} to close
	 * @return false if there was a problem.
	 */
	public boolean closeCNFUse(CNFUse cnf) {
		if (cnf == null)
			return true;

		boolean res = true;
		if (!cnf.getCnf().close())
			res = false;

		for (CNFUse tmp : cnf.getPreproc().values())
			if (!closeCNFUse(tmp))
				res = false;

		return res;
	}

	/**
	 * Maj the shortest path from the first cnf to result. (shortest means with the smaller
	 * execution time).
	 * 
	 * @return a list of {@link SessionBlock} representing the path from the first to the result.
	 *         null if there is none.
	 */
	public List<SessionBlock> majShortestPath() {
		CNFUseStatus status = first.getStatus();
		if (status == CNFUseStatus.TO_BE_DETERMINED)
			return null;// pas de parcour a chercher


		shortestPath = majShortestPathCNF(first, Long.MAX_VALUE);
		return shortestPath;
	}

	/**
	 * Return the shortest path from the cnf to result. (shortest means with the smaller execution
	 * time).
	 * 
	 * @return a list of {@link SessionBlock} representing the path from the cnf to the result. null
	 *         if there is none.
	 */
	private List<SessionBlock> majShortestPathCNF(CNFUse cnf, long max) {
		List<SessionBlock> res = null;
		;
		long newmax = max;

		if (cnf.getPreviousExecutionTime() >= max)
			return null;

		if (cnf.getStatus() == first.getStatus())
		{// doit trouver l'exec la plus courte
			SessionBlock last = null;
			for (PreprocessorExecutionBlock pre : cnf.getPreproc().keySet())
			{// check les preproc
				if (pre.getStatus() != ExecutionStatus.FINISH
						|| (pre.getRes().getStatus() != PreProcessorResultStatus.SATISFIABLE && pre.getRes().getStatus() != PreProcessorResultStatus.UNSATISFIABLE))
					continue;// pas la peine de chercher

				long tmp = pre.getTotalExecutionTime();
				if (tmp < newmax)
				{// nouveau plus cour chemin
					newmax = tmp;
					last = pre;
				}
			}
			for (SolverExecutionBlock sol : cnf.getSolver())
			{// check les solver
				if (sol.getStatus() != ExecutionStatus.FINISH
						|| (sol.getRes().getStatus() != SolverResultStatus.SATISFIABLE && sol.getRes().getStatus() != SolverResultStatus.UNSATISFIABLE))
					continue;// pas la peine de chercher

				long tmp = sol.getTotalExecutionTime();
				if (tmp < newmax)
				{// nouveau plus cour chemin
					newmax = tmp;
					last = sol;
				}
			}

			if (last != null)
			{// a trouver
				res = new LinkedList<SessionBlock>();
				res.add(cnf);
				res.add(1, last);
				// return res;
			}

		}


		List<SessionBlock> path = null;
		Map<PreprocessorExecutionBlock, CNFUse> preprocs = cnf.getPreproc();
		for (PreprocessorExecutionBlock block : preprocs.keySet())
		{// cherche le plus cour chemin dans les preprocs
			path = majShortestPathCNF(preprocs.get(block), newmax);
			if (path != null)
			{// chemin plus cour trouver
				res = path;
				res.add(0, block);
				res.add(0, cnf);
				newmax = path.get(path.size() - 1).getTotalExecutionTime();
			}
		}
		return res;
	}


	/**
	 * Return the shortest path from the first cnf to result. (shortest means with the smaller
	 * execution time).
	 * 
	 * @return a list of {@link SessionBlock} representing the path from the first to the result.
	 *         null if there is none.
	 */
	public List<SessionBlock> getShortestPath() {
		return shortestPath;
	}

	/**
	 * Export the data in XML.
	 * 
	 * @param parent
	 *            parent of the xml directory
	 * @param directory
	 *            name of the directory you want to export the session
	 * @return false if something goes wrong
	 */
	public boolean exportXML(File parent, String directory) {
		File dir = new File(parent, directory);
		File export = new File(dir, XMLBaliseFormat.XML_FILE_NAME);

		if (!parent.isDirectory() || dir.exists())
			return false;

		BufferedWriter buf = null;
		try
		{
			if (!dir.mkdir() || !export.createNewFile())
			{// pb sort supprime tout
				dir.delete();
				return false;
			}
			buf = new BufferedWriter(new FileWriter(export));

			buf.write(XMLBaliseFormat.XML_PROLOG);
			System.out.println("export la first");
			first.exportXML(buf, dir);

		} catch (IOException e)
		{
			e.printStackTrace();
			if (buf != null)
				try
				{
					buf.close();
				} catch (IOException e1)
				{
					e1.printStackTrace();
				}

			dir.delete();
			return false;
		}

		try
		{
			buf.close();
		} catch (IOException e1)
		{
			e1.printStackTrace();
			dir.delete();
			return false;
		}

		return true;
	}

}
